const express = require('express');
const app = express();
const httpProxyMiddleware = require("http-proxy-middleware");
const zlib = require('zlib');
const fs = require("fs");
const http = require("http");
const https = require("https");
const { program } = require('commander');
const allProxyMap = new Map();
const allProxyHost = [];
const cacheFileName = "./.cache";
const pidFileName = "./pid";
const config = require("./config");
const request = require('request');

app.get('/time', (req, res) => {
  res.send(String(new Date().getTime()))
});

async function getBodyBuffer(proxyRes) {
  return new Promise((resolve, reject) => {
    let body = [];
    proxyRes.on('data', function (chunk) {
      body.push(chunk)
    });
    let bodyStr = "";
    proxyRes.on('end', function () {
      if (!proxyRes.complete) {
        console.error('The connection was terminated while the message was still being sent');
      }

      bodyStr = Buffer.concat(body);
      resolve(bodyStr)
    });
  })
}

function decodeBuffer(buffer, encodingType) {
  let decodeMap = new Map([
    ['gzip', 'gunzipSync'],
    ['deflate', 'inflateSync'],
    ['br', 'brotliDecompressSync'],
  ]);
  if (decodeMap.has(encodingType)) {
    return  zlib[decodeMap.get(encodingType)](buffer);
  }

  return buffer;
}
const onProxyRes = async (proxyRes, req, res) => {
  console.info(`[APP] proxy res statusCode=${proxyRes.statusCode}`, req.url);

  let bodyBuffer = await getBodyBuffer(proxyRes);
  const contentEncoding = proxyRes.headers['content-encoding'];
  if (contentEncoding!==undefined) {
    bodyBuffer = decodeBuffer(bodyBuffer, contentEncoding);
  }

  const contentType = proxyRes.headers['content-type'];
  if (!(contentType.indexOf("text")>=0 || contentType.indexOf("application")>=0)) {
    res.end(bodyBuffer);
    return
  }

  let bodyStr = bodyBuffer.toString();

  config.ReplaceRules.forEach(r => {
    bodyStr = bodyStr.replace(new RegExp(r.from, "gm"), r.to)
  });

  const rule = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/g;
  const urls = bodyStr.match(rule);
  (urls||[]).forEach((urlStr) => {
    let url = new URL(urlStr);
    let uri = registerProxy(url);
    let proxyUrl = getProxy() + uri;
    if (url.pathname!=='/') {
      proxyUrl = url.pathname;
    }

    // console.log(`${urlStr} to ${proxyUrl}`);
    bodyStr = bodyStr.replace(new RegExp(urlStr, "gm"), proxyUrl);
  });

  bodyStr = bodyStr.replace(new RegExp("http://127.0.0.1:7777/http/www.w3.org", "gm"), "http://www.w3.org")
  //设置允许跨域的域名，*代表允许任意域名跨域
  // res.header("Access-Control-Allow-Origin", "*");
  // //允许的header类型
  // res.header("Access-Control-Allow-Headers", "content-type");
  // //跨域允许的请求方式
  // res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
  res.header('content-type', proxyRes.headers['content-type']);
  console.log("content-type", proxyRes.headers['content-type']);
  if (proxyRes.statusCode!==404) {
    res.writeHead(proxyRes.statusCode, proxyRes.statusMessage);
    res.end(bodyStr);
  } else {
    console.warn(proxyRes.statusCode, proxyRes.statusMessage);
    let url = req.url.replace(/^\/https\//, "https://");
    url = url.replace(/^\/http\//, "http://");
    request.get({url:url, headers:{"host":(new URL(url)).host}}, (error, response, body) => {
      console.log("response", response);
      res.writeHead(response.statusCode, response.statusMessage);

      res.end(body);
    })
  }
};

const onProxyReq = async (proxyReq, req, res, options) => {
  console.info(`[APP] proxy req url=${req.url}`);

  const indexOfHost = proxyReq.path.indexOf(proxyReq.host);
  if (indexOfHost>=0) {
    proxyReq.path = proxyReq.path.substr(indexOfHost+proxyReq.host.length);
    console.log("proxyReq.path", proxyReq.path);
  } else {
    console.warn("onProxyReq path not change", proxyReq);
  }

  const KeyOfReferer = "referer";
  const headers = proxyReq.getHeaders();

  if (proxyReq.hasHeader(KeyOfReferer)) {
    const refererUrl = new URL(headers[KeyOfReferer]);
    const rule = /(\/https\/|\/http\/)((\w|=|\?|\.|\/|&|-)+)/g;
    if (refererUrl.toString().indexOf(getProxy(req))>=0 && refererUrl.pathname.match(rule)) {
      let pathname = refererUrl.toString().substring(getProxy().length);
      pathname = pathname.replace(/^\/https\//, "https://");
      pathname = pathname.replace(/^\/http\//, "http://");
      let proxyRefererUrl = new URL(pathname + "/" + refererUrl.search);
      proxyReq.setHeader(KeyOfReferer, proxyRefererUrl.toString());
      proxyReq.setHeader("origin", proxyRefererUrl.origin);
      proxyReq.setHeader("host", proxyRefererUrl.host);
    }
  }
};

/**
 *
 * @param url
 * @returns {string}
 */
const registerProxy = (url) => {
  const uri = `/${url.protocol.replace(":", "")}/${url.host}`;
  const host = url.origin;

  if (allProxyMap.has(uri)) {
    return uri;
  }

  // console.log(`[APP] Proxy ${uri} -> ${host}`);
  let proxy =  httpProxyMiddleware.createProxyMiddleware({
    target: host,
    changeOrigin:true,
    selfHandleResponse:true,
    onProxyReq:onProxyReq,
    onProxyRes:onProxyRes,
  });
  allProxyMap.set(uri, proxy);
  allProxyHost.push(host);
  app.use(uri, proxy);

  try {
    fs.writeFileSync(cacheFileName, JSON.stringify(allProxyHost));
  } catch (e) {
  }

  return uri
};

app.use("/redirect", async (req, res) => {
  const KeyOfTarget = "target";
  if (req.query[KeyOfTarget]) {
    let target = decodeURIComponent(req.query[KeyOfTarget]);

    registerProxy(new URL(target));

    target = target.replace(/^https:\/\//, "/https/");
    target = target.replace(/^http:\/\//, "/http/");
    target = getProxy() + target;
    res.redirect(target);
    return ;
  }

  res.end(`${req.path} target is not set`);
});

function getProxy() {
  let proxy = new URL(program.opts()["proxy"]||program.opts()["host"]);
  return proxy.toString().replace(/\/$/, "");
}

function start(server) {
  if (fs.existsSync(pidFileName)) {
    const oldPid = Number(fs.readFileSync(pidFileName));
    try {
      process.kill(oldPid);
    } catch (e) {
    }
  }

  fs.writeFileSync(pidFileName, String(process.pid));

  try {
    let content = fs.readFileSync(cacheFileName);
    content = JSON.parse(content);
    content.forEach((url) => {
      registerProxy(new URL(url));
    })
  } catch (e) {
  }

  try {
    if (server.protocol==='http:') {
      console.log(`http server listening at ${server.toString()}, proxy is ${getProxy()}`);
      http.createServer(app).listen(server.port, server.hostname)
    } else if (server.protocol==='https:') {
      console.log(`https server listening at ${server.toString()}, proxy is ${getProxy()}`);
      https.createServer(app).listen(server.port, server.hostname)
    }
  } catch (e) {
    console.error(e);
  }
}

program
  .requiredOption('--host <http[s]://[ip]:[port]>', 'node服务监听地址')
  .option('--proxy <http[s]://[ip]:[port]>', '代理地址，如nginx代理到node');

program.parse();
try {
  let server = new URL(program.opts()["host"]);
  start(server);
} catch (e) {
  console.error(`host ${program.opts()["host"]} is invalid, please check`, e);
  return ;
}

